package com.ojoin.trade.common_core.web.shiro;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.ojoin.trade.common.web.service.impl.UserUtils;
import com.ojoin.trade.common_core.web.domain.TbMerchantInfo;
import com.ojoin.trade.common_core.web.service.TbMerchantInfoBaseService;
import org.apache.commons.lang3.StringUtils;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.base.Strings;
import com.ojoin.trade.common.Constants;
import com.ojoin.trade.common.constdef.IConstDef;
import com.ojoin.trade.common.utils.BeanMapUtils;
import com.ojoin.trade.common.utils.CommonUtils;
import com.ojoin.trade.common.utils.LogUtils;
import com.ojoin.trade.common.utils.SpringUtils;
import com.ojoin.trade.common.utils.json.JSONUtils;
import com.ojoin.trade.common.web.domain.Button;
import com.ojoin.trade.common.web.domain.Menu;
import com.ojoin.trade.common.web.domain.Role;
import com.ojoin.trade.common.web.domain.SessionUser;
import com.ojoin.trade.common.web.domain.User;
import com.ojoin.trade.common.web.jcaptcha.JCaptcha;
import com.ojoin.trade.common.web.ldap.LdapSettings;
import com.ojoin.trade.common.web.ldap.LdapSettingsService;
import com.ojoin.trade.common.web.ldap.support.LdapConnector;
import com.ojoin.trade.common.web.ldap.support.LdapEntry;
import com.ojoin.trade.common.web.ldap.support.TrustAllX509TrustManager;
import com.ojoin.trade.common.web.service.IRSAService;
import com.ojoin.trade.common.web.service.SysManageService;
import com.ojoin.trade.common.web.shiro.Principal;
import com.ojoin.trade.common.web.shiro.UsernamePasswordToken;
import com.ojoin.trade.common_core.web.domain.MerchantUserInfo;
import com.ojoin.trade.common_core.web.service.FactoringCompanyBaseService;
import com.ojoin.trade.common_core.web.service.FactoringOperatorBaseService;
import com.ojoin.trade.common_core.web.service.MerchantUserBaseService;

/**
 * 系统安全认证实现类
 */
@Component
public class SystemAuthorizingRealm extends AuthorizingRealm {

	private Logger logger = LoggerFactory.getLogger(getClass());

	private FactoringOperatorBaseService operateservice;

	private SysManageService systemService;

	private MerchantUserBaseService payUserService;

	private FactoringCompanyBaseService companyservice;


	private TbMerchantInfoBaseService userinfoService;

	private LdapConnector ldapConnector;

	private LdapSettingsService ldapSettingsService;

	private IRSAService rsa;

	public SystemAuthorizingRealm() {
		setCredentialsMatcher(new AllowAllCredentialsMatcher());
	}

	/*
	 * 认证密码匹配调用方法
	 * 
	 * @Override protected void assertCredentialsMatch(AuthenticationToken
	 * authcToken, AuthenticationInfo info) throws AuthenticationException { }
	 */

	/**
	 * 认证回调函数, 登录时调用
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authcToken) {
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;

		String loginType = token.getLoginType();
		String username = token.getUsername().trim();
		String password = null;
		if (token.getPassword() != null) {
			password = new String(token.getPassword());
			password = getRSAService().decryptStringByJs(password);
		}

		User user;

		if (IConstDef.loginType_IpayNow.equals(loginType)) {
			// 校验登录验证码
			boolean ret = JCaptcha.hasCaptcha(CommonUtils.getRequest(), token
					.getCaptcha().toLowerCase().trim());
			if (!ret) {
				throw new AuthenticationException("prefix:验证码错误，请重试！");
			}

			// 登录现在支付
			List rmRet = null;
			try {
				rmRet = getMerchantUserBaseService().login(username.trim(),
						password);
			} catch (Exception e) {
				logger.error("登录现在支付异常", e);
				throw new AuthenticationException("prefix:登录失败！");
			}
			if (CommonUtils.isSuccess(rmRet)) {

				Map<String, String> data = (Map) rmRet.get(1);
				SessionUser sessionUser = new SessionUser();
				MerchantUserInfo merchUser = null;
				try {
					sessionUser = (SessionUser) BeanMapUtils.toBean(
							SessionUser.class, data);
				} catch (Exception e) {
					logger.error("现在支付用户获取异常", e);
					throw new AuthenticationException("prefix:登录失败！");
				}
				String mchNo = data.get(Constants.CURRENT_USERMHTNO);

				List payUserlist = getMerchantUserBaseService().queryPayUserVOByID(mchNo);
				if (CommonUtils.isSuccess(payUserlist)) {
					Map<String, String> payUserdata = (Map) payUserlist.get(1);
					String result = payUserdata.get("mhtData");
					merchUser = JSONUtils.parseObject(result,MerchantUserInfo.class);
					merchUser.setLoginNo(sessionUser.getLoginNo());
					sessionUser.setLoginName(sessionUser.getLoginNo());
					sessionUser.setMchType(merchUser.getMchType());
					sessionUser.setMobile(merchUser.getMobile());
					sessionUser.setRealStatus(merchUser.getRealStatus());
					sessionUser.setMchName(merchUser.getMchName());

					// 关联九盈用户
					int joinid = getMerchantUserBaseService().registerJoinUser( mchNo, username);
					sessionUser.setId(joinid);
					// 九盈本地实名信息
					 TbMerchantInfo muInfo=getUserinfoService().selectRealnameInfoByJoinid(joinid);
					if(null!=muInfo&&StringUtils.isNotEmpty(muInfo.getMobile())) {
						sessionUser.setMobile(muInfo.getMobile());
					}

					logger.info("查询商户信息 loginNO {} mchNo  {} ", new String[] { username, mchNo });

				} else {
					logger.error(String.valueOf(CommonUtils.buildError(
							payUserlist).getMessage()));
					throw new AuthenticationException("prefix:" + "获取商户信息异常");
				}

				Session session = CommonUtils.getShiroSession();
				session.setAttribute(Constants.SESSION_LOGINUSER, sessionUser);
				session.setAttribute(Constants.SESSION_MERCHANTUSER, merchUser);

				user = new User();
				user.setId(sessionUser.getId());
				user.setLoginName(token.getUsername());
				if (null != merchUser) {
					user.setName(merchUser.getName());
				}
				user.setLoginType(loginType);

				// 默认密码明文匹配
				return new SimpleAuthenticationInfo(new Principal(user),
						token.getPassword(), getName());
			} else {
				String rmError = String.valueOf(CommonUtils.buildError(rmRet)
						.getMessage());
				throw new AuthenticationException("prefix:" + rmError);
			}

		} else if (IConstDef.loginType_Factor.equals(loginType)) {
			// 校验用户名密码
			user = getSystemService().getByLoginName(username);
			if (user != null) {
				if (Constants.NO.equals(user.getLoginFlag())) {
					throw new AuthenticationException("prefix:该已帐号禁止登录.");
				}

				if (!UserUtils.validatePassword(password, user.getPassword())) {
					throw new AuthenticationException("prefix:账号或密码错误.");
				}

				user.setLoginType(loginType);
				return new SimpleAuthenticationInfo(new Principal(user),
						token.getPassword(), getName());
			} else {

				AuthenticationInfo info = ldapLogin(token);
				if (null == info) {
					throw new AuthenticationException("prefix:登录失败.");
				} else {
					return info;
				}
			}

		} else if (IConstDef.loginType_Factor_Wechat.equals(loginType)) {

			// 校验用户名密码
			user = getSystemService().getByLoginName(username);
			if (user != null) {
				if (Constants.NO.equals(user.getLoginFlag())) {
					throw new AuthenticationException("prefix:该已帐号禁止登录.");
				}

				user.setLoginType(loginType);
				return new SimpleAuthenticationInfo(new Principal(user),
						token.getPassword(), getName());
			} else {
				throw new AuthenticationException("prefix:登录失败.");
			}

		} else {
			throw new AuthenticationException("prefix:登录失败.");
		}
	}

	/**
	 * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		Principal principal = (Principal) getAvailablePrincipal(principals);

		User user = getSystemService().getByLoginName(principal.getLoginName());
		if (user != null) {
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			// 添加用户有权限的菜单信息
			List<Menu> menulist = getSystemService().getMenuList();
			for (Menu menu : menulist) {
				if (StringUtils.isNotBlank(menu.getPermission())) {
					// 添加基于Permission的权限信息
					for (String permission : StringUtils.split(
							menu.getPermission(), ",")) {
						info.addStringPermission(permission);
					}
				}
			}

			// 添加用户有权限的按钮信息
			List<Button> buttonlist = getSystemService().getButtonList();
			for (Button button : buttonlist) {
				if (StringUtils.isNotBlank(button.getPermission())) {
					// 添加基于Permission的权限信息
					for (String permission : StringUtils.split(
							button.getPermission(), ",")) {
						info.addStringPermission(permission);
					}
				}
			}

			// 添加用户权限
			info.addStringPermission("user");
			// 添加用户角色信息
			for (Role role : user.getRoleList()) {
				info.addRole(role.getEnname());
			}
			// 更新登录IP和时间
			getSystemService().updateUserLoginInfo(user);
			// 记录登录日志
			LogUtils.info(logger, "登录用户%s", user.getLoginName());
			return info;
		} else {
			return null;
		}
	}

	@Override
	protected void checkPermission(Permission permission, AuthorizationInfo info) {
		authorizationValidate(permission);
		super.checkPermission(permission, info);
	}

	@Override
	protected boolean[] isPermitted(List<Permission> permissions,
			AuthorizationInfo info) {
		if (permissions != null && !permissions.isEmpty()) {
			for (Permission permission : permissions) {
				authorizationValidate(permission);
			}
		}
		return super.isPermitted(permissions, info);
	}

	@Override
	public boolean isPermitted(PrincipalCollection principals,
			Permission permission) {
		authorizationValidate(permission);
		return super.isPermitted(principals, permission);
	}

	@Override
	protected boolean isPermittedAll(Collection<Permission> permissions,
			AuthorizationInfo info) {
		if (permissions != null && !permissions.isEmpty()) {
			for (Permission permission : permissions) {
				authorizationValidate(permission);
			}
		}
		return super.isPermittedAll(permissions, info);
	}

	/**
	 * 授权验证方法
	 * 
	 * @param permission
	 */
	private void authorizationValidate(Permission permission) {
		// 模块授权预留接口
	}

	private AuthenticationInfo ldapLogin(UsernamePasswordToken token) {

		final LdapConnectionConfig config = new LdapConnectionConfig();
		final LdapSettings ldapSettings = getLdapSettingsService().load();
		if (ldapSettings == null || !ldapSettings.isEnabled()) {
			logger.trace("LDAP is disabled, skipping");
			throw new AuthenticationException("prefix:LDAP禁用.");
		}
		config.setLdapHost(ldapSettings.getUri().getHost());
		config.setLdapPort(ldapSettings.getUri().getPort());
		config.setUseSsl(ldapSettings.getUri().getScheme().startsWith("ldaps"));
		config.setUseTls(ldapSettings.isUseStartTls());
		if (ldapSettings.isTrustAllCertificates()) {
			config.setTrustManagers(new TrustAllX509TrustManager());
		}
		config.setName(ldapSettings.getSystemUserName());
		config.setCredentials(ldapSettings.getSystemPassword());

		final String principal = (String) token.getPrincipal();
		if (Strings.isNullOrEmpty(principal) || token.getPassword() == null) {
			// do not try to look a token up in LDAP if there is no principal or
			// password
			return null;
		}
		LdapNetworkConnection connection = null;
		try {
			connection = getLdapConnector().connect(config);

			if (null == connection) {
				logger.error("Couldn't connect to LDAP directory");
				throw new AuthenticationException("prefix:LDAP 服务无法连接.");
			}

			String password = null;
			if (token.getPassword() != null) {
				password = getRSAService().decryptStringByJs(
						new String(token.getPassword()));
			}

			final LdapEntry userEntry = getLdapConnector().search(connection,
					ldapSettings.getSearchBase(),
					ldapSettings.getSearchPattern(),
					ldapSettings.getDisplayNameAttribute(), principal,
					ldapSettings.isActiveDirectory(),
					ldapSettings.getGroupSearchBase(),
					ldapSettings.getGroupIdAttribute(),
					ldapSettings.getGroupSearchPattern());
			if (userEntry == null) {
				logger.debug("User {} not found in LDAP", principal);
				throw new AuthenticationException("prefix:用户不存在.");
			}

			// needs to use the DN of the entry, not the parameter for the
			// lookup filter we used to find the entry!
			final boolean authenticated = getLdapConnector().authenticate(
					connection, userEntry.getDn(), password);
			if (!authenticated) {
				logger.info("Invalid credentials for user {} (DN {})",
						principal, userEntry.getDn());
				throw new AuthenticationException("prefix:账号或密码错误.");
			}
			// user found and authenticated, sync the user entry with db
			final User user = getSystemService().syncFromLdapEntry(userEntry,
					ldapSettings, principal);
			if (user == null) {
				// in case there was an error reading, creating or modifying the
				// user in db, we do not authenticate the user.
				logger.error("Unable to sync LDAP user {} (DN {})",
						userEntry.getBindPrincipal(), userEntry.getDn());
				throw new AuthenticationException("prefix:账号 同步错误 LDAP.");
			}

			user.setLoginType(IConstDef.loginType_Factor);
			return new SimpleAuthenticationInfo(new Principal(user), null,
					"ldap realm");
		} catch (LdapException e) {
			logger.error("LDAP error", e);
		} catch (CursorException e) {
			logger.error("Unable to read LDAP entry", e);
		} catch (Exception e) {
			logger.error(
					"Error during LDAP user account sync. Cannot log in user {}",
					principal, e);
		} finally {
			if (connection != null) {
				try {
					connection.close();
				} catch (IOException e) {
					logger.error("Unable to close LDAP connection", e);
				}
			}
		}
		return null;

	}

	protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
		if(null!=principals){
			Principal principal = (Principal) getAvailablePrincipal(principals);
			return principal.getLoginName();
		}
		return principals;
	}

	/**
	 * 获取系统业务对象
	 */
	public SysManageService getSystemService() {
		if (systemService == null) {
			systemService = SpringUtils.getBean(SysManageService.class);
		}
		return systemService;
	}

	/**
	 * 获取系统RSA
	 */
	public IRSAService getRSAService() {
		if (rsa == null) {
			rsa = SpringUtils.getBean(IRSAService.class);
		}
		return rsa;
	}

	/**
	 * 获取现在支付登录服务
	 */
	public MerchantUserBaseService getMerchantUserBaseService() {
		if (payUserService == null) {
			payUserService = SpringUtils.getBean(MerchantUserBaseService.class);
		}
		return payUserService;
	}

	public FactoringOperatorBaseService getOperateservice() {
		if (operateservice == null) {
			operateservice = SpringUtils
					.getBean(FactoringOperatorBaseService.class);
		}
		return operateservice;
	}

	public FactoringCompanyBaseService getCompanyservice() {
		if (companyservice == null) {
			companyservice = SpringUtils
					.getBean(FactoringCompanyBaseService.class);
		}
		return companyservice;
	}

	public TbMerchantInfoBaseService getUserinfoService() {
		if (userinfoService == null) {
			userinfoService = SpringUtils
					.getBean(TbMerchantInfoBaseService.class);
		}
		return userinfoService;
	}

	private LdapConnector getLdapConnector() {
		if (null == ldapConnector) {
			ldapConnector = SpringUtils.getBean(LdapConnector.class);
		}
		return ldapConnector;
	}

	private LdapSettingsService getLdapSettingsService() {
		if (null == ldapSettingsService) {
			ldapSettingsService = SpringUtils
					.getBean(LdapSettingsService.class);
		}
		return ldapSettingsService;
	}

}
